<template>
  <span v-if="props.showText">{{ message }}</span>
  <el-select
    v-else
    v-model="selectValue"
    filterable
    clearable
    popper-class="remote-select-box"
    :allow-create="allowCreate"
    :disabled="inputDisabled"
    :placeholder="calcPlaceholder"
    :filter-method="filterMethod"
    :popper-append-to-body="true"
    @focus="handleFocus"
    @blur="handleBlur"
    @change="handleChange"
    @clear="handleClear"
    @click.stop="handleFocus"
  >
    <template #empty>
      <span class="empty">{{ loading ? '数据加载中...' : tipText }}</span>
    </template>
    <span class="empty">{{ loading ? '数据加载中...' : tipText }}</span>
    <span v-if="options.length > 0">
      <el-option
        v-for="item in options"
        :key="item.id"
        :label="(item.name || '').length > 30 ? ((item.name || '').substring(0, 30) + '...') : (item.name || '')"
        :title="(item.name || '').length > 30 ? (item.name || '') : ''"
        :value="item.id"
      ></el-option>
    </span>
  </el-select>
</template>


<script setup>
import {ref, watch, onMounted, computed, onUnmounted, nextTick} from 'vue';
import {useDisabled} from 'element-plus';
import api from '@/http/api';

const emit = defineEmits(['update:modelValue', 'change', 'blur', 'clear', 'update:name']);
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  query: Object,
  option: Object,
  dataUrl: String,
  filterName: {
    type: String,
    default: 'name'
  },
  allowCreate: {
    type: Boolean,
    default: false
  },
  filterable: {
    type: Boolean,
    default: false
  },
  pageSize: {
    type: Number,
    default: 10,
  },
  placeholder: {
    type: String,
    default: '--请选择--'
  },
  // 查询数据中id的字段名
  idProp: {
    type: String,
    default: 'id'
  },
  // 显示的数据字段名(来源于查询的数据)，多个用逗号分隔，格式化示例：name(sex)
  nameProp: {
    type: String,
    default: 'name'
  },
  selectFirst: { // 是否初始化选中第1个值
    type: Boolean,
    default: false
  },
  showText: Boolean
});

const loading = ref(false);
const tipText = ref('请输入内容搜索');
const timer = ref(null);
const options = ref([]);
const message = ref('');
const inputDisabled = useDisabled();
const selectValue = ref(props.modelValue || '');
const changeLoad = ref(false);
const selectFirst = ref(false);
const pageNumber = ref(1);
const filter = ref(false);
const updateValue = ref(false);

const calcPlaceholder = computed(() => {
  return inputDisabled.value ? ' ' : props.placeholder;
});

const handleFocus = () => {
  filter.value = false;
  changeLoad.value = true;
  if (options.value.length <= 1) {
    loadData(props.query);
  }
};

const handleBlur = () => {
  filter.value = false;
  changeLoad.value = false;
};

const handleChange = () => {
  filter.value = false;
  if (!selectValue.value) {
    changeInput('', '');
    return;
  }

  let currOpt = options.value.find(item => item.id === selectValue.value);
  if (currOpt) {
    changeInput(currOpt.id, currOpt.name);
  } else if (props.option && props.option.id === selectValue.value) {
    options.value.push(props.option);
  }
};

const initSelectFirst = () => {
  if (!inputDisabled.value
      && props.selectFirst === true
      && selectFirst.value === false
      && (props.modelValue || '') === ''
      && options.value.length > 0) {
    selectFirst.value = true;
    changeInput(options.value[0].id, options.value[0].name);
  }
};

const changeInput = (id, name) => {
  updateValue.value = true;
  emit('update:modelValue', id);
  emit('update:name', name);
  emit('change', {id: id, name: name});
  selectValue.value = id;
  message.value = name;

  nextTick(() => {
    updateValue.value = false;
  });
};

const handleClear = () => {
  pageNumber.value = 1;
  selectValue.value = '';
  changeLoad.value = true;
  filter.value = false;
  options.value = [];
  changeInput('', '');

  nextTick(() => {
    changeLoad.value = false;
  });
  return true;
};

const filterMethod = (value) => {
  if (updateValue.value) {
    return;
  }

  filter.value = true;
  pageNumber.value = 1;
  let params = {};
  params[props.filterName] = value;
  loadData(params);
};

const loadData = (params) => {
  if (!changeLoad.value) {
    return;
  }
  if (loading.value) {
    if (timer.value != null) {
      clearTimeout(timer.value);
    }
    timer.value = setTimeout(function () {
      loadData(params);
    }, 500);
  } else {
    if (timer.value != null) {
      clearTimeout(timer.value);
    }
    timer.value = setTimeout(function () {
      timer.value = null;
      doLoadData(params);
    }, 500);
  }
};

const doLoadData = (params) => {
  if (params == null) {
    params = { pageSize: props.pageSize };
  } else {
    params.pageSize = props.pageSize
  }
  params = Object.assign({pageNumber: pageNumber.value++}, params, props.query);
  loading.value = true;
  options.value = [];
  api.post(props.dataUrl, params).then(res => {
    if (timer.value == null && res.code === '1') {
      if (res.data && res.data.length > 0) {
        let arr = [];
        res.data.forEach((item) => {
          let labelName = generateName(props.nameProp, item);
          if ((item[props.idProp] || '') !== '') {
            arr.push({ id: item[props.idProp], name: labelName, data: item })
          }
        })
        options.value = arr;
        tipText.value = '请输入内容搜索';
      } else {
        tipText.value = '暂无数据';
      }

      if (!filter.value) {
        calcValue();
        initSelectFirst();
      }
    }

    if (timer.value != null) {
      tipText.value = '数据加载中...';
    }
    loading.value = false;
    timer.value = null;
  }).catch(() => {
    loading.value = false;
    timer.value = null;
  })
};

const calcValue = () => {
  if (options.value == null || options.value.length <= 0) {
    selectValue.value = '';
    message.value = '';
  } else {
    let currOpt = options.value.find(item => item.id === selectValue.value);
    if (currOpt) {
      message.value = currOpt[props.nameProp];
    } else {
      selectValue.value = '';
      message.value = '';
    }
  }
};

const generateName = (name, json) => {
  return name.replace(/[0-9a-zA-Z]+/g, function(s) {
    return json[s] || '';
  });
};

const init = (data) => {
  options.value = [];
  selectValue.value = data.modelValue || '';

  let option = data.option || {};
  if (option.id && option.id !== "") {
    options.value.unshift(option);
    selectValue.value = option.id;
    message.value = option.name;
  }

  if (!inputDisabled.value && data.selectFirst === true) {
    doLoadData();
  }selectValue.value = data.modelValue || '';
  message.value = '';
  let currOpt = options.value.find(item => item.id === selectValue.value);
  if (selectValue.value && !currOpt) {
    options.value = [];
    let option = data.option || {};
    if (option.id && option.id !== '' && selectValue.value === option.id) {
      options.value.unshift(option);
      selectValue.value = option.id;
      message.value = option.name;
    }

    if (!inputDisabled.value && data.selectFirst === true) {
      doLoadData();
    }
  } else if (currOpt) {
    selectValue.value = currOpt.id || '';
    message.value = currOpt.name || '';
  }
};

watch(() => props, (data) => {
  if (updateValue.value) {
    return;
  }

  init(data);
}, {deep: true});

watch(() => props.query, (newVal, oldVal) => {
  let change = false;
  for (let p in newVal) {
    if ((oldVal[p] || "") !== "" && newVal[p] !== oldVal[p]) {
      change = true;
    }
  }

  if (change) {
    options.value = [];
    selectValue.value = '';
  }
}, {deep: true});

onMounted(() => {
  options.value = [];
  selectValue.value = props.modelValue || '';

  let option = props.option || {};
  if (option.id && option.id !== '') {
    options.value.unshift(option);
    selectValue.value = option.id;
    message.value = option.name;
  }

  if (!inputDisabled.value && props.selectFirst === true) {
    doLoadData();
  }
});

onUnmounted(() => {
  clearTimeout(timer.value);
  options.value = [];
  message.value = '';
});
</script>

<style>
.remote-select-box .el-select-dropdown__wrap {
  max-height: 70vh;
  text-align: left;
}
</style>
<style scoped>
.empty {
  font-size: 14px;
  padding: 0 20px;
  position: relative;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #606266;
  height: 40px;
  line-height: 40px;
  box-sizing: border-box;
}
</style>
